Extra-2: 추천시스템

추천시스템
Author

최규빈

Published

December 21, 2022

추천시스템– 나는 솔로, 커피vs홍차

강의영상

https://youtube.com/playlist?list=PLQqh36zP38-xvbBbDqgRJiSneNaCUkdsX

imports

import torch
import numpy as np 
import pandas as pd
from fastai.collab import * 

나는 솔로

주절주절 intro

- Data

df_view = pd.read_csv('https://raw.githubusercontent.com/guebin/STML2022/main/posts/V.%20RecSys/2022-12-21-rcmdsolo.csv',index_col=0)
df_view 
영식 영철 영호 광수 상철 영수
옥순 3.9 4.1 NaN 0.5 0.3 NaN
영자 4.5 NaN 3.7 0.5 NaN 0.2
정숙 NaN 4.9 4.7 NaN 1.2 1.3
영숙 0.6 0.2 NaN 4.1 4.3 NaN
순자 0.7 0.9 NaN 4.2 NaN 3.9
현숙 NaN 0.2 0.3 NaN 3.5 3.4

- 데이터를 이해할때 필요한 가정들 – 내맘대로 한 설정임.

  • (옥순,영자,정숙)은 (영식,영철,영호)와 성격이 잘 맞고 (영숙,순자,현숙)은 (광수,상철,영수)와 성격이 잘맞음
  • ((옥순,영자,정숙),(영식,영철,영호))은 MBTI가 I로 시작하고 ((영숙,순자,현숙),(광수,상철,영수))는 MBTI가 E로 시작한다.

- 목표: NaN 을 추론

- 수동추론:

  • (옥순,영호)이 만난다면? \(\to\) 둘다 I성향이니까 잘 맞지 않을까? \(\to\) 4.0 정도?
  • (정숙,영식)조합은? \(\to\) 둘다 I성향이니까 잘 맞지 않을까? + 정숙은 다 잘맞던데..? \(\to\) 4.8 정도?
  • (현숙,영식)조합은? \(\to\) 현숙은 E성향인데 영식은 I성향이므로 잘 안맞을 것임 + 현숙은 원래 좀 눈이 높음 \(\to\) 0.25 정도?

- 좀 더 체계적인 추론

사람들이 가지고 있는 성향들을 두 개의 숫자로 표현하자.

  • 옥순의 성향 = (I성향,E성향) = (1.9, 0.0)
  • 영식의 성향 = (I성향,E성향) = (2.0, 0.1)
  • 현숙의 성향 = (I성향,E성향) = (0.0, 1.5)
a1 = np.array([1.9,0.0]).reshape(2,1) 
b1= np.array([2.0,0.1]).reshape(2,1)
sum(a1*b1)
array([3.8])

(1) 옥순과 영식의 궁합 \(\approx\) 옥순의I성향\(\times\)영식의I성향 \(+\) 옥순의E성향\(\times\)영식의E성향 // 적합

a1= np.array([1.9,0.0]).reshape(2,1) # a1은 옥순의 성향, col-vec으로 선언하자. 
b1= np.array([2.0,0.1]).reshape(2,1) # b1은 영식의 성향, col-vec으로 선언하자.
(a1*b1).sum()
3.8

(2) 현숙과 영식의 궁합 \(\approx\) 현숙의I성향\(\times\)영식의I성향 \(+\) 현숙의E성향\(\times\)영식의E성향 // 예측

a6= np.array([0.0,1.5]).reshape(2,1)
(a6*b1).sum()
0.15000000000000002
  • 그럴듯함..

- 모델링

아래가 같음을 관찰하라. (차원만 다름)

(a1*b1).sum(), a1.T@b1
(3.8, array([[3.8]]))
(a6*b1).sum(), a6.T@b1
(0.15000000000000002, array([[0.15]]))

만약에 여자의성향, 남자의성향을 적당한 매트릭스로 정리할 수 있다면 궁합매트릭스를 만들 수 있음

a1= np.array([1.9,0.0]).reshape(2,1)
a2= np.array([2.0,0.1]).reshape(2,1)
a3= np.array([2.5,1.0]).reshape(2,1)
a4= np.array([0.1,1.9]).reshape(2,1)
a5= np.array([0.2,2.1]).reshape(2,1)
a6= np.array([0.0,1.5]).reshape(2,1)
A = np.concatenate([a1,a2,a3,a4,a5,a6],axis=1)
A
array([[1.9, 2. , 2.5, 0.1, 0.2, 0. ],
       [0. , 0.1, 1. , 1.9, 2.1, 1.5]])
b1= np.array([2.0,0.1]).reshape(2,1)
b2= np.array([1.9,0.2]).reshape(2,1)
b3= np.array([1.8,0.3]).reshape(2,1)
b4= np.array([0.3,2.1]).reshape(2,1)
b5= np.array([0.2,2.0]).reshape(2,1)
b6= np.array([0.1,1.9]).reshape(2,1)
B = np.concatenate([b1,b2,b3,b4,b5,b6],axis=1)
B
array([[2. , 1.9, 1.8, 0.3, 0.2, 0.1],
       [0.1, 0.2, 0.3, 2.1, 2. , 1.9]])
A.T@B
array([[3.8 , 3.61, 3.42, 0.57, 0.38, 0.19],
       [4.01, 3.82, 3.63, 0.81, 0.6 , 0.39],
       [5.1 , 4.95, 4.8 , 2.85, 2.5 , 2.15],
       [0.39, 0.57, 0.75, 4.02, 3.82, 3.62],
       [0.61, 0.8 , 0.99, 4.47, 4.24, 4.01],
       [0.15, 0.3 , 0.45, 3.15, 3.  , 2.85]])
a1.T@b1, a2.T@b2, a3.T@b1
(array([[3.8]]), array([[3.82]]), array([[5.1]]))

결국 모형은 아래와 같다.

\[\text{궁합매트릭스} = {\bf A}^\top {\bf B} + \text{오차}\]

- 학습전략: 아래의 매트릭스중에서 어떤값은 관측하였고 어떤값은 관측하지 못함 \(\to\) 관측한 값들만 대충 비슷하게 하면 되는거 아니야?

A.T@B 
array([[3.8 , 3.61, 3.42, 0.57, 0.38, 0.19],
       [4.01, 3.82, 3.63, 0.81, 0.6 , 0.39],
       [5.1 , 4.95, 4.8 , 2.85, 2.5 , 2.15],
       [0.39, 0.57, 0.75, 4.02, 3.82, 3.62],
       [0.61, 0.8 , 0.99, 4.47, 4.24, 4.01],
       [0.15, 0.3 , 0.45, 3.15, 3.  , 2.85]])
df_view
영식 영철 영호 광수 상철 영수
옥순 3.9 4.1 NaN 0.5 0.3 NaN
영자 4.5 NaN 3.7 0.5 NaN 0.2
정숙 NaN 4.9 4.7 NaN 1.2 1.3
영숙 0.6 0.2 NaN 4.1 4.3 NaN
순자 0.7 0.9 NaN 4.2 NaN 3.9
현숙 NaN 0.2 0.3 NaN 3.5 3.4

- 자료를 아래와 같이 정리한다면?

df = pd.DataFrame([(f,m,df_view.loc[f,m]) for f in df_view.index for m in df_view.columns if not np.isnan(df_view.loc[f,m])])
df.columns = ['X1','X2','y']
df
X1 X2 y
0 옥순 영식 3.9
1 옥순 영철 4.1
2 옥순 광수 0.5
3 옥순 상철 0.3
4 영자 영식 4.5
5 영자 영호 3.7
6 영자 광수 0.5
7 영자 영수 0.2
8 정숙 영철 4.9
9 정숙 영호 4.7
10 정숙 상철 1.2
11 정숙 영수 1.3
12 영숙 영식 0.6
13 영숙 영철 0.2
14 영숙 광수 4.1
15 영숙 상철 4.3
16 순자 영식 0.7
17 순자 영철 0.9
18 순자 광수 4.2
19 순자 영수 3.9
20 현숙 영철 0.2
21 현숙 영호 0.3
22 현숙 상철 3.5
23 현숙 영수 3.4
mapp1 = {k[1]:k[0] for k in enumerate(df.X1.unique())}
mapp2 = {k[1]:k[0] for k in enumerate(df.X2.unique())}
mapp1,mapp2
({'옥순': 0, '영자': 1, '정숙': 2, '영숙': 3, '순자': 4, '현숙': 5},
 {'영식': 0, '영철': 1, '광수': 2, '상철': 3, '영호': 4, '영수': 5})
X1 = torch.tensor(list(map(lambda name: mapp1[name], df.X1)))
X2 = torch.tensor(list(map(lambda name: mapp2[name], df.X2)))
X1 = torch.nn.functional.one_hot(X1).float()
X2 = torch.nn.functional.one_hot(X2).float()
y = torch.tensor(df.y).float()

- yhat을 구하는 과정..

l1 = torch.nn.Linear(in_features=6,out_features=2) 
l2 = torch.nn.Linear(in_features=6,out_features=2)
l1(X1) # 옥순~현숙의 성향들 
tensor([[-0.3442,  0.0280],
        [-0.3442,  0.0280],
        [-0.3442,  0.0280],
        [-0.3442,  0.0280],
        [ 0.2028,  0.1404],
        [ 0.2028,  0.1404],
        [ 0.2028,  0.1404],
        [ 0.2028,  0.1404],
        [ 0.1110,  0.0959],
        [ 0.1110,  0.0959],
        [ 0.1110,  0.0959],
        [ 0.1110,  0.0959],
        [-0.3608, -0.2406],
        [-0.3608, -0.2406],
        [-0.3608, -0.2406],
        [-0.3608, -0.2406],
        [-0.3450, -0.2079],
        [-0.3450, -0.2079],
        [-0.3450, -0.2079],
        [-0.3450, -0.2079],
        [ 0.3332, -0.2464],
        [ 0.3332, -0.2464],
        [ 0.3332, -0.2464],
        [ 0.3332, -0.2464]], grad_fn=<AddmmBackward0>)
l2(X2) # 영식~영수의 성향들 
tensor([[-0.0777, -0.0163],
        [-0.1901, -0.3110],
        [-0.3925, -0.4103],
        [-0.3446, -0.2070],
        [-0.0777, -0.0163],
        [-0.2570, -0.2648],
        [-0.3925, -0.4103],
        [-0.1879,  0.1743],
        [-0.1901, -0.3110],
        [-0.2570, -0.2648],
        [-0.3446, -0.2070],
        [-0.1879,  0.1743],
        [-0.0777, -0.0163],
        [-0.1901, -0.3110],
        [-0.3925, -0.4103],
        [-0.3446, -0.2070],
        [-0.0777, -0.0163],
        [-0.1901, -0.3110],
        [-0.3925, -0.4103],
        [-0.1879,  0.1743],
        [-0.1901, -0.3110],
        [-0.2570, -0.2648],
        [-0.3446, -0.2070],
        [-0.1879,  0.1743]], grad_fn=<AddmmBackward0>)

- 몇개의 관측치만 생각해보자..

df.head()
X1 X2 y
0 옥순 영식 3.9
1 옥순 영철 4.1
2 옥순 광수 0.5
3 옥순 상철 0.3
4 영자 영식 4.5
(l1(X1)[0]*l2(X2)[0]).sum() # (옥순의성향 * 영식의성향).sum()
tensor(0.0263, grad_fn=<SumBackward0>)
  • 이 값이 실제로는 3.9 이어야 한다.
(l1(X1)[1]*l2(X2)[1]).sum() # (옥순의성향 * 영철의성향).sum()
tensor(0.0567, grad_fn=<SumBackward0>)
  • 이 값이 실제로는 4.1 이어야 한다.

- yhat을 구하면!

yhat = (l1(X1) * l2(X2)).sum(axis=1) # (l1(X1) * l2(X2)).sum(1)와 결과가 같음 
yhat
tensor([ 0.0263,  0.0567,  0.1236,  0.1128, -0.0180, -0.0893, -0.1372, -0.0136,
        -0.0509, -0.0539, -0.0581, -0.0041,  0.0319,  0.1434,  0.2403,  0.1741,
         0.0302,  0.1303,  0.2207,  0.0286,  0.0133, -0.0204, -0.0638, -0.1055],
       grad_fn=<SumBackward1>)
yhat[:2],y[:2] # 이 값들이 비슷해야 하는데..
(tensor([0.0263, 0.0567], grad_fn=<SliceBackward0>), tensor([3.9000, 4.1000]))

- 0~5 까지의 범위로 고정되어 있으니까 아래와 같이 해도 되겠음..

sig = torch.nn.Sigmoid()
yhat = sig((l1(X1) * l2(X2)).sum(axis=1))*5 # (l1(X1) * l2(X2)).sum(1)와 결과가 같음 
yhat
tensor([2.5329, 2.5709, 2.6543, 2.6409, 2.4774, 2.3884, 2.3288, 2.4829, 2.4363,
        2.4326, 2.4274, 2.4948, 2.5399, 2.6790, 2.7990, 2.7171, 2.5377, 2.6626,
        2.7748, 2.5357, 2.5166, 2.4745, 2.4203, 2.3682],
       grad_fn=<MulBackward0>)
loss = torch.mean((y-yhat)**2)
loss
tensor(3.3187, grad_fn=<MeanBackward0>)

torch를 이용한 학습

torch.manual_seed(43052)
l1 = torch.nn.Linear(6,2) 
l2 = torch.nn.Linear(6,2)
sig = torch.nn.Sigmoid() 
loss_fn = torch.nn.MSELoss() 
optimizr = torch.optim.Adam(list(l1.parameters())+list(l2.parameters()))
for epoc in range(5000):
    ## 1 
    feature1 = l1(X1)
    feature2 = l2(X2) 
    matching_score = (feature1*feature2).sum(axis=1) 
    yhat = sig(matching_score)*5 # 만약에 1~3점이라면 "1+sig(matching_score)*2" 와 같이 하면 되었을듯 
    ## 2 
    loss = loss_fn(yhat,y)    
    ## 3 
    loss.backward()    
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat
tensor([3.9382, 4.0624, 0.4665, 0.3353, 4.5038, 3.6975, 0.3562, 0.3558, 4.8614,
        4.7208, 1.1813, 1.3158, 0.4606, 0.3573, 4.1288, 4.2734, 0.8611, 0.7347,
        4.0493, 4.0464, 0.1810, 0.3124, 3.5031, 3.3948],
       grad_fn=<MulBackward0>)
y
tensor([3.9000, 4.1000, 0.5000, 0.3000, 4.5000, 3.7000, 0.5000, 0.2000, 4.9000,
        4.7000, 1.2000, 1.3000, 0.6000, 0.2000, 4.1000, 4.3000, 0.7000, 0.9000,
        4.2000, 3.9000, 0.2000, 0.3000, 3.5000, 3.4000])
l1(X1) # 두번째 칼럼이 I 성향 점수로 "해석"된다
tensor([[-1.4663,  0.2938],
        [-1.4663,  0.2938],
        [-1.4663,  0.2938],
        [-1.4663,  0.2938],
        [-1.7086,  0.6597],
        [-1.7086,  0.6597],
        [-1.7086,  0.6597],
        [-1.7086,  0.6597],
        [-0.8705,  1.2945],
        [-0.8705,  1.2945],
        [-0.8705,  1.2945],
        [-0.8705,  1.2945],
        [ 1.1046, -0.8298],
        [ 1.1046, -0.8298],
        [ 1.1046, -0.8298],
        [ 1.1046, -0.8298],
        [ 0.9880, -0.5193],
        [ 0.9880, -0.5193],
        [ 0.9880, -0.5193],
        [ 0.9880, -0.5193],
        [ 0.6834, -1.2201],
        [ 0.6834, -1.2201],
        [ 0.6834, -1.2201],
        [ 0.6834, -1.2201]], grad_fn=<AddmmBackward0>)
  • 포인트: 여성출연자중, 정숙은 대체로 잘 맞춰주고 현숙은 그렇지 않았음.. \(\to\) 그러한 가중치가 잘 드러남!!

fastai를 이용한 학습

(1) dls

df.head() # 앞단계 전처리의 산물
X1 X2 y
0 옥순 영식 3.9
1 옥순 영철 4.1
2 옥순 광수 0.5
3 옥순 상철 0.3
4 영자 영식 4.5
dls = CollabDataLoaders.from_df(df,bs=2,valid_pct=2/24)

(2) lrnr 생성

lrnr = collab_learner(dls,n_factors=2,y_range=(0,5))

(3) 학습

lrnr.fit(30,lr=0.05)
epoch train_loss valid_loss time
0 3.889854 1.921828 00:00
1 3.429675 1.831365 00:00
2 2.782349 0.825877 00:00
3 2.016374 0.019480 00:00
4 1.467516 0.151512 00:00
5 1.098058 0.277732 00:00
6 0.836384 0.322864 00:00
7 0.646015 0.336643 00:00
8 0.504761 0.351580 00:00
9 0.398490 0.375448 00:00
10 0.316750 0.407963 00:00
11 0.253128 0.439455 00:00
12 0.203320 0.458523 00:00
13 0.163978 0.461103 00:00
14 0.133211 0.474495 00:00
15 0.108348 0.493935 00:00
16 0.088889 0.492454 00:00
17 0.073574 0.489081 00:00
18 0.061007 0.485161 00:00
19 0.050791 0.512837 00:00
20 0.042383 0.501657 00:00
21 0.035439 0.485179 00:00
22 0.029940 0.490993 00:00
23 0.025290 0.489756 00:00
24 0.021683 0.487266 00:00
25 0.019516 0.472549 00:00
26 0.018497 0.484601 00:00
27 0.017128 0.427455 00:00
28 0.015667 0.524356 00:00
29 0.013884 0.385270 00:00

(4) 예측

적합값 확인

lrnr.show_results()
X1 X2 y y_pred
0 3.0 6.0 3.7 4.572644
1 6.0 2.0 3.5 3.595040

(옥순의 궁합)

df_new = pd.DataFrame({'X1':['옥순']*6, 'X2':['영식','영철','영호','광수','상철','영수']})
df_new
X1 X2
0 옥순 영식
1 옥순 영철
2 옥순 영호
3 옥순 광수
4 옥순 상철
5 옥순 영수
lrnr.get_preds(dl=dls.test_dl(df_new))
(tensor([3.8813, 4.0784, 3.9177, 0.5294, 0.3296, 0.4189]), None)

비교를 위해서

df_view
영식 영철 영호 광수 상철 영수
옥순 3.9 4.1 NaN 0.5 0.3 NaN
영자 4.5 NaN 3.7 0.5 NaN 0.2
정숙 NaN 4.9 4.7 NaN 1.2 1.3
영숙 0.6 0.2 NaN 4.1 4.3 NaN
순자 0.7 0.9 NaN 4.2 NaN 3.9
현숙 NaN 0.2 0.3 NaN 3.5 3.4

(정숙의 궁합)

df_new = pd.DataFrame({'X1':['정숙']*6, 'X2':['영식','영철','영호','광수','상철','영수']})
df_new
X1 X2
0 정숙 영식
1 정숙 영철
2 정숙 영호
3 정숙 광수
4 정숙 상철
5 정숙 영수
lrnr.get_preds(dl=dls.test_dl(df_new))
(tensor([4.6639, 4.8092, 4.7264, 1.7349, 1.1538, 1.3598]), None)

비교를 위해서

df_view
영식 영철 영호 광수 상철 영수
옥순 3.9 4.1 NaN 0.5 0.3 NaN
영자 4.5 NaN 3.7 0.5 NaN 0.2
정숙 NaN 4.9 4.7 NaN 1.2 1.3
영숙 0.6 0.2 NaN 4.1 4.3 NaN
순자 0.7 0.9 NaN 4.2 NaN 3.9
현숙 NaN 0.2 0.3 NaN 3.5 3.4

- Appedix: fastai 구조공부..

lrnr.model
EmbeddingDotBias(
  (u_weight): Embedding(7, 2)
  (i_weight): Embedding(7, 2)
  (u_bias): Embedding(7, 1)
  (i_bias): Embedding(7, 1)
)
lrnr.model.forward??
Signature: lrnr.model.forward(x)
Docstring:
Defines the computation performed at every call.
Should be overridden by all subclasses.
.. note::
    Although the recipe for forward pass needs to be defined within
    this function, one should call the :class:`Module` instance afterwards
    instead of this since the former takes care of running the
    registered hooks while the latter silently ignores them.
Source:   
    def forward(self, x):
        users,items = x[:,0],x[:,1]
        dot = self.u_weight(users)* self.i_weight(items)
        res = dot.sum(1) + self.u_bias(users).squeeze() + self.i_bias(items).squeeze()
        if self.y_range is None: return res
        return torch.sigmoid(res) * (self.y_range[1]-self.y_range[0]) + self.y_range[0]
File:      ~/anaconda3/envs/py37/lib/python3.7/site-packages/fastai/collab.py
Type:      method
  • bias를 제외하면 우리가 짠 모형과 같음!

커피 or 홍차

data

- 예전에 살펴본 예제

df = pd.read_csv('https://raw.githubusercontent.com/guebin/DL2022/main/posts/I.%20Overview/2022-09-08-rcmd_anal.csv')
df
user item rating item_name
0 1 15 1.084308 홍차5
1 1 1 4.149209 커피1
2 1 11 1.142659 홍차1
3 1 5 4.033415 커피5
4 1 4 4.078139 커피4
... ... ... ... ...
995 100 18 4.104276 홍차8
996 100 17 4.164773 홍차7
997 100 14 4.026915 홍차4
998 100 4 0.838720 커피4
999 100 7 1.094826 커피7

1000 rows × 4 columns

- 기억을 살리기 위해서..

df_view = pd.read_csv('https://raw.githubusercontent.com/guebin/DL2022/main/posts/I.%20Overview/2022-09-08-rcmd_view.csv')
df_view
커피1 커피2 커피3 커피4 커피5 커피6 커피7 커피8 커피9 커피10 홍차1 홍차2 홍차3 홍차4 홍차5 홍차6 홍차7 홍차8 홍차9 홍차10
0 4.149209 NaN NaN 4.078139 4.033415 4.071871 NaN NaN NaN NaN 1.142659 1.109452 NaN 0.603118 1.084308 NaN 0.906524 NaN NaN 0.903826
1 4.031811 NaN NaN 3.822704 NaN NaN NaN 4.071410 3.996206 NaN NaN 0.839565 1.011315 NaN 1.120552 0.911340 NaN 0.860954 0.871482 NaN
2 4.082178 4.196436 NaN 3.956876 NaN NaN NaN 4.450931 3.972090 NaN NaN NaN NaN 0.983838 NaN 0.918576 1.206796 0.913116 NaN 0.956194
3 NaN 4.000621 3.895570 NaN 3.838781 3.967183 NaN NaN NaN 4.105741 1.147554 NaN 1.346860 NaN 0.614099 1.297301 NaN NaN NaN 1.147545
4 NaN NaN NaN NaN 3.888208 NaN 3.970330 3.979490 NaN 4.010982 NaN 0.920995 1.081111 0.999345 NaN 1.195183 NaN 0.818332 1.236331 NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
95 0.511905 1.066144 NaN 1.315430 NaN 1.285778 NaN 0.678400 1.023020 0.886803 NaN 4.055996 NaN NaN 4.156489 4.127622 NaN NaN NaN NaN
96 NaN 1.035022 NaN 1.085834 NaN 0.812558 NaN 1.074543 NaN 0.852806 3.894772 NaN 4.071385 3.935935 NaN NaN 3.989815 NaN NaN 4.267142
97 NaN 1.115511 NaN 1.101395 0.878614 NaN NaN NaN 1.329319 NaN 4.125190 NaN 4.354638 3.811209 4.144648 NaN NaN 4.116915 3.887823 NaN
98 NaN 0.850794 NaN NaN 0.927884 0.669895 NaN NaN 0.665429 1.387329 NaN NaN 4.329404 4.111706 3.960197 NaN NaN NaN 3.725288 4.122072
99 NaN NaN 1.413968 0.838720 NaN NaN 1.094826 0.987888 NaN 1.177387 3.957383 4.136731 NaN 4.026915 NaN NaN 4.164773 4.104276 NaN NaN

100 rows × 20 columns

모형

(편의상 바이어스를 제외하면)

- 특징벡터:

  • 유저1의 취향 = [커피를 좋아하는 정도, 홍차를 좋아하는 정도]
  • 아이템1의 특징 = [커피의 특징, 홍차인 특징]

- 평점

  • 유저1이 아이템1을 먹었을경우 평점: 유저1의 취향과 아이템1의 특징의 내적 = (유저1의 취향 \(\odot\) 아이템1의 특징).sum()

학습

(1) dls

dls = CollabDataLoaders.from_df(df)
dls.items
user item rating item_name
86 9 15 1.004437 홍차5
254 26 10 3.794259 커피10
960 97 20 4.267142 홍차10
825 83 6 0.771229 커피6
574 58 19 4.016329 홍차9
... ... ... ... ...
264 27 1 4.042835 커피1
759 76 19 3.715771 홍차9
46 5 13 1.081111 홍차3
72 8 5 4.022260 커피5
211 22 9 3.709681 커피9

800 rows × 4 columns

(2) lrnr

lrnr = collab_learner(dls,n_factors=2) # 교재에는 y_range 를 설정하도록 되어있지만 설정 안해도 적합에는 크게 상관없음..

(3) fit

lrnr.fit(10,0.1)
epoch train_loss valid_loss time
0 5.639192 2.679791 00:00
1 3.614457 1.906409 00:00
2 2.405080 0.399353 00:00
3 1.641215 0.217753 00:00
4 1.173295 0.130624 00:00
5 0.861192 0.096714 00:00
6 0.647233 0.078799 00:00
7 0.495189 0.077169 00:00
8 0.384622 0.071355 00:00
9 0.303257 0.071730 00:00

(4) predict

(적합된 값 확인)

lrnr.show_results() # 누를때마다 결과다름
user item rating rating_pred
0 16.0 3.0 4.199087 3.899566
1 90.0 11.0 3.780528 4.062635
2 46.0 4.0 4.196852 4.435931
3 8.0 1.0 4.194341 3.967788
4 80.0 10.0 0.943593 1.080764
5 61.0 19.0 4.160296 3.965987
6 63.0 8.0 0.772492 1.177593
7 82.0 5.0 0.916553 1.179026
8 13.0 18.0 1.012880 0.861018

(예측값)

df_new = pd.DataFrame({'user':[1,1,1,1], 'item':[9,10,11,12]})
df_new
user item
0 1 9
1 1 10
2 1 11
3 1 12
lrnr.get_preds(dl=dls.test_dl(df_new))
(tensor([3.9750, 4.0915, 1.0473, 0.9544]), None)